﻿using System;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Configuration;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Trentacular.SharePoint.Features.Util
{
    public enum SPWebConfigModificationType
    {
        Add,
        Remove
    }

    public enum WebConfigEntryActionType
    {
        Add,
        Remove
    }

    public interface ISectionGroupElement
    {
        SectionGroup Parent { get; set; }
    }

    public class SectionGroup<T> : SectionGroup where T : ConfigurationSectionGroup
    {
        public SectionGroup() { Type = typeof(T); }
    }

    public class SectionGroup : ISectionGroupElement
    {
        public SectionGroup Parent { get; set; }
        public string Name { get; set; }
        public Type Type { get; set; }
    }

    public class Section<T> : Section where T : ConfigurationSection
    {
        public Section() { Type = typeof(T); }
    }

    public class Section : ISectionGroupElement
    {
        public SectionGroup Parent { get; set; }
        public string Name { get; set; }
        public Type Type { get; set; }
        public bool RequirePermission { get; set; }
        public ConfigurationAllowDefinition AllowDefinition { get; set; }
    }

    public class SPWebConfigModificationHelper
    {
        public string Owner { get; private set; }
        public SPWebApplication WebApp { get; private set; }

        private Collection<SPWebConfigModification> WebConfigModifications { get { return WebApp.WebConfigModifications; } }

        public SPWebConfigModificationHelper(SPWebApplication webApp, string owner)
        {
            Owner = owner;
            WebApp = webApp;
        }

        public SPWebConfigModificationHelper(SPWebApplication webApp, SPFeatureReceiverProperties properties)
            : this(webApp, properties.Definition.Id.ToString()) { }

        #region Modifiers

        public void Modify(SPWebConfigModification modification, SPWebConfigModificationType type)
        {
            switch (type)
            {
                case SPWebConfigModificationType.Add:
                    WebConfigModifications.Add(modification);
                    break;
                case SPWebConfigModificationType.Remove:
                    WebConfigModifications.Remove(modification);
                    break;
            }
        }

        public void ModifyCompilationAssembly(string assemblyName, SPWebConfigModificationType type)
        {
            Modify(CreateCompilationAssemblyModification(assemblyName), type);
        }

        public void ModifyCompilationAssembly(Assembly assembly, SPWebConfigModificationType type)
        {
            ModifyCompilationAssembly(assembly.FullName, type);
        }

        public void ModifyHttpModule<T>(string name, WebConfigEntryActionType actionType, SPWebConfigModificationType type) where T : IHttpModule
        {
            Modify(CreateHttpModuleModification<T>(name, actionType), type);
        }

        public void ModifyHttpHandler(HttpHandlerAction handler, WebConfigEntryActionType actionType, SPWebConfigModificationType type)
        {
            Modify(CreateHttpHandlerModification(handler, actionType), type);
        }

        public void ModifyAppSetting(string key, string value, SPWebConfigModificationType type)
        {
            Modify(CreateAppSettingModification(key, value), type);
        }

        public void ModifySectionGroup(SectionGroup sectionGroup, SPWebConfigModificationType type)
        {
            Modify(CreateSectionGroupModification(sectionGroup), type);
        }

        public void ModifySection(Section section, SPWebConfigModificationType type)
        {
            Modify(CreateSectionModification(section), type);
        }

        public void ModifySafeControl(Assembly assembly, string nameSpace, string typeName, bool isSafe, SPWebConfigModificationType type)
        {
            Modify(CreateSafeControlModification(assembly, nameSpace, typeName, isSafe), type);
        }

        public void EnsureControlsSection()
        {
            Modify(CreateControlsSectionModification(), SPWebConfigModificationType.Add);
        }

        public void ModifyControl(string tagPrefix, string nameSpace, Assembly assembly, SPWebConfigModificationType type)
        {
            Modify(CreateControlModification(tagPrefix, nameSpace, assembly.FullName), type);
        }

        public void ModifyControl(string tagPrefix, string nameSpace, string assemblyName, SPWebConfigModificationType type)
        {
            Modify(CreateControlModification(tagPrefix, nameSpace, assemblyName), type);
        }

        public void ModifyDependentAssembly(string name, string publicKeyToken, string oldVersion, string newVersion, SPWebConfigModificationType type)
        {
            Modify(CreateDependentAssemblyModification(name, publicKeyToken, oldVersion, newVersion), type);
        }

        public void ModifyRootSection(string name, string value, SPWebConfigModificationType type)
        {
            Modify(CreateRootModification(name, value), type);
        }

        /// <summary>
        /// Remove any modifications that were originally created by the owner
        /// </summary>
        public void RemoveAllOwnedModifications()
        {
            for (int i = WebConfigModifications.Count - 1; i >= 0; i--)
            {
                var modification = WebConfigModifications[i];
                if (modification.Owner == Owner)
                    WebConfigModifications.Remove(modification);
            }
        }

        #endregion

        public void SaveChanges()
        {
            WebApp.Update();
            WebApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

            // TODO: Will the following line work just as well?
            //WebApp.WebService.ApplyWebConfigModifications();
        }

        #region Modification Builders

        public SPWebConfigModification CreateCompilationAssemblyModification(string assemblyName)
        {
            string modName = "add[@assembly='" + assemblyName + "']";
            string modXPath = "configuration/system.web/compilation/assemblies";
            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = string.Format(@"<add assembly=""{0}"" />", assemblyName)
            };
        }

        public SPWebConfigModification CreateAppSettingModification(string key, string value)
        {
            string modName = "add[@key='" + key + "']";
            string modXPath = "configuration/appSettings";
            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = string.Format(@"<add key=""{0}"" value=""{1}"" />",
                    key,
                    value)
            };
        }

        private void BuildSectionGroupXPath(StringBuilder modXPath, ISectionGroupElement element)
        {
            if (element.Parent == null)
                return;

            var parent = element.Parent;
            modXPath.Insert(0, string.Format("/sectionGroup[@name='{0}']", parent.Name));

            BuildSectionGroupXPath(modXPath, parent);
        }

        public SPWebConfigModification CreateSectionGroupModification(SectionGroup sectionGroup)
        {
            string modName = @"sectionGroup[@name='" + sectionGroup.Name + "']";
            StringBuilder modXPath = new StringBuilder();
            BuildSectionGroupXPath(modXPath, sectionGroup);
            modXPath.Insert(0, "configuration/configSections");

            return new SPWebConfigModification(modName, modXPath.ToString())
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = string.Format(@"<sectionGroup name=""{0}"" type=""{1}"" />",
                    sectionGroup.Name,
                    sectionGroup.Type.AssemblyQualifiedName)
            };
        }

        public SPWebConfigModification CreateSectionModification(Section section)
        {
            string modName = @"section[@name='" + section.Name + "']";
            StringBuilder modXPath = new StringBuilder();
            BuildSectionGroupXPath(modXPath, section);
            modXPath.Insert(0, "configuration/configSections");

            return new SPWebConfigModification(modName, modXPath.ToString())
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = string.Format(@"<section name=""{0}"" type=""{1}"" requirePermission=""{2}"" allowDefinition=""{3}"" />",
                    section.Name,
                    section.Type.AssemblyQualifiedName,
                    section.RequirePermission.ToString().ToLower(),
                    section.AllowDefinition)
            };
        }

        public SPWebConfigModification CreateSafeControlModification(Assembly assembly, string nameSpace, string typeName, bool isSafe)
        {
            string modName = @"SafeControl[@Assembly='" + assembly.FullName + "' and @Namespace='" + nameSpace + "' and @TypeName='" + typeName + "']";
            string modXPath = "configuration/SharePoint/SafeControls";

            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = string.Format(@"<SafeControl Assembly=""{0}"" Namespace=""{1}"" TypeName=""{2}"" Safe=""{3}"" />",
                    assembly.FullName,
                    nameSpace,
                    typeName,
                    isSafe)
            };
        }

        public SPWebConfigModification CreateHttpHandlerModification(HttpHandlerAction handler, WebConfigEntryActionType actionType)
        {
            string action = actionType.ToString().ToLower();
            string modName = string.Format("../httpHandlers[{0}]/{1}[@verb='{2}' and @path='{3}']",
                actionType == WebConfigEntryActionType.Remove ? "0=0" : "1=1",
                action,
                handler.Verb,
                handler.Path);

            string modXPath = "configuration/system.web/httpHandlers";
            var value = new StringBuilder()
                .AppendFormat(@"<{0} verb=""{1}"" path=""{2}""", action, handler.Verb, handler.Path);

            if (actionType == WebConfigEntryActionType.Add)
                value.AppendFormat(@" validate=""{0}"" type=""{1}""", handler.Validate, handler.Type);

            value.Append(" />");

            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = value.ToString()
            };
        }

        public SPWebConfigModification CreateHttpModuleModification<T>(string name, WebConfigEntryActionType actionType) where T : IHttpModule
        {
            string action = actionType.ToString().ToLower();
            string modName = string.Format("../httpModules[{0}]/{1}[@name='{2}']",
                actionType == WebConfigEntryActionType.Remove ? "0=0" : "1=1",
                action,
                name);

            string modXPath = "configuration/system.web/httpModules";
            var value = new StringBuilder()
                .AppendFormat(@"<{0} name=""{1}""", action, name);

            if (actionType == WebConfigEntryActionType.Add)
                value.AppendFormat(@" type=""{0}""", typeof(T).AssemblyQualifiedName);

            value.Append(" />");

            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = value.ToString()
            };
        }

        private SPWebConfigModification CreateControlsSectionModification()
        {
            string modName = "controls";
            string modXPath = "configuration/system.web/pages";
            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = GetType().AssemblyQualifiedName,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = "<controls />"
            };
        }

        public SPWebConfigModification CreateControlModification(string tagPrefix, string nameSpace, string assemblyName)
        {
            string modName = string.Format("add[@tagPrefix='{0}' and @namespace='{1}']", tagPrefix, nameSpace);
            string modXPath = "configuration/system.web/pages/controls";
            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = string.Format(@"<add tagPrefix=""{0}"" namespace=""{1}"" assembly=""{2}"" />", tagPrefix, nameSpace, assemblyName)
            };
        }

        public SPWebConfigModification CreateDependentAssemblyModification(string name, string publicKeyToken, string oldVersion, string newVersion)
        {
            string modName = string.Format("*[local-name()='dependentAssembly'][*/@name='{0}'][*/@publicKeyToken='{1}']", name, publicKeyToken);
            string modXPath = "configuration/runtime/*[local-name()='assemblyBinding' and namespace-uri()='urn:schemas-microsoft-com:asm.v1']"; // filtering using the tag local name and namespace-uri
            return new SPWebConfigModification(modName, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = string.Format(
                    "<dependentAssembly><assemblyIdentity name='{0}' publicKeyToken='{1}' /><bindingRedirect oldVersion='{2}' newVersion='{3}' /></dependentAssembly>",
                    name,
                    publicKeyToken,
                    oldVersion,
                    newVersion)
            };
        }

        public SPWebConfigModification CreateRootModification(string name, string value)
        {
            string modXPath = "configuration";
            return new SPWebConfigModification(name, modXPath)
            {
                Owner = Owner,
                Sequence = 0,
                Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
                Value = value
            };
        }

        #endregion
    }
}
